﻿using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using PooledGrowableBufferHelper;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

namespace WholeSlideImageLibrary.Formats.ImageSharp
{
    public class ImageSharpImageIdentifier : IImageIdentifier
    {
        public string Name => "ImageSharp";

        public int HeaderSize => 0;

        public async ValueTask<ImageSourceType> IdentifyAsync(FileSourceInfo fileInfo, FileSourceReader reader, ReadOnlyMemory<byte> header, CancellationToken cancellationToken = default)
        {
            if (fileInfo is null)
            {
                throw new ArgumentNullException(nameof(fileInfo));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (fileInfo is IPhysicalFileSource physicalFileSource)
            {
                using var fs = new FileStream(physicalFileSource.PhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                await Image.IdentifyAsync(Configuration.Default, fs, cancellationToken).ConfigureAwait(false);
                return ImageSourceType.SingleImage;
            }

            if (fileInfo.FileSize > 256 * 1024 * 1024) // This file is unreasonablely large.
            {
                return ImageSourceType.None;
            }

            using (PooledMemoryStream? stream = PooledMemoryStreamManager.Shared.GetStream())
            {
                await CopyAsync(reader, stream, fileInfo.FileSize, cancellationToken).ConfigureAwait(false);
                stream.Seek(0, SeekOrigin.Begin);
                Image.Identify(stream);
                return ImageSourceType.SingleImage;
            }
        }

        private const int MaximumSegmentSize = 16384;

        private static async Task CopyAsync(FileSourceReader source, IBufferWriter<byte> destination, long length, CancellationToken cancellationToken)
        {
            long offset = 0;
            while (length > 0)
            {
                Memory<byte> memory = destination.GetMemory((int)Math.Min(length, MaximumSegmentSize));
                int bytesToRead = (int)Math.Min(length, memory.Length);
                int bytesRead = await source.ReadAsync(offset, memory.Slice(0, bytesToRead), cancellationToken).ConfigureAwait(false);
                if (bytesRead != bytesToRead)
                {
                    throw new IOException();
                }
                destination.Advance(bytesToRead);
                offset += bytesToRead;
                length -= bytesToRead;
            }
        }

        public ValueTask<ImageSourceType> IdentifyAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            return new ValueTask<ImageSourceType>(ImageSourceType.None);
        }

        public ValueTask<WholeSlideImageCollection?> OpenImageCollectionAsync(FileSourceInfo fileInfo, FileSourceReader reader, CancellationToken cancellationToken = default)
        {
            return default;
        }

        public ValueTask<WholeSlideImageCollection?> OpenImageCollectionAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            return default;
        }

        public async ValueTask<WholeSlideImage?> OpenSingleImageAsync(FileSourceInfo fileInfo, FileSourceReader reader, CancellationToken cancellationToken = default)
        {
            if (fileInfo is null)
            {
                throw new ArgumentNullException(nameof(fileInfo));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (fileInfo is IPhysicalFileSource physicalFileSource)
            {
                using var fs = new FileStream(physicalFileSource.PhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                Image<Rgba32>? image = await Image.LoadAsync<Rgba32>(fs).ConfigureAwait(false);
                return new ImageSharpWholeSlideImage(image);
            }

            if (fileInfo.FileSize > 256 * 1024 * 1024) // This file is unreasonablely large.
            {
                return null;
            }

            using (PooledMemoryStream? stream = PooledMemoryStreamManager.Shared.GetStream())
            {
                await CopyAsync(reader, stream, fileInfo.FileSize, cancellationToken).ConfigureAwait(false);
                stream.Seek(0, SeekOrigin.Begin);
                var image = Image.Load<Rgba32>(stream);
                return new ImageSharpWholeSlideImage(image);
            }
        }

        public ValueTask<WholeSlideImage?> OpenSingleImageAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            return default;
        }
    }
}
